/**
 * Copyright (C) 2008 by The Regents of the University of California
 * Redistribution of this file is permitted under the terms of the GNU
 * Public License (GPL).
 *
 * @author Junghoo "John" Cho <cho AT cs.ucla.edu>
 * @date 3/24/2008
 */

#include <cstdio>
#include <iostream>
#include <fstream>
#include "Bruinbase.h"
#include "SqlEngine.h"
#include "BTreeIndex.h"

using namespace std;

// external functions and variables for load file and sql command parsing 
extern FILE* sqlin;
int sqlparse(void);


RC SqlEngine::run(FILE* commandline)
{
  fprintf(stdout, "Bruinbase> ");

  // set the command line input and start parsing user input
  sqlin = commandline;
  sqlparse();  // sqlparse() is defined in SqlParser.tab.c generated from
               // SqlParser.y by bison (bison is GNU equivalent of yacc)

  return 0;
}

RC SqlEngine::select(int attr, const string& table, const vector<SelCond>& cond)
{
    RecordFile rf;   // RecordFile containing the table
    RecordId   rid;  // record cursor for table scanning

    RC     rc;
    int    key;     
    string value;
    int    count;
    int    diff;

    // open the table file
    if ((rc = rf.open(table + ".tbl", 'r')) < 0) {
        fprintf(stderr, "Error: table %s does not exist\n", table.c_str());
        return rc;
    }
    
    count = 0;
    
    // analyze query to determine best usage for index
    bool useIndex = true; // whether or not using an index is necessary
    bool onlyKeys = true; // whether or not the actual records need to be accessed
    
    int minKey = INT_MIN; // minimum for range
    bool includeMin = true; // GT or GE
    int maxKey = INT_MAX; // maximum for range
    bool includeMax = true; // LT or LE
    
    int equalsKey = 0; // in case of EQ
    bool useEqualsKey = false; // EQ
    
    vector<size_t> nonRangeConds; // vector to used when processing via the index to skip over redundant range conditions
    
    // check if index exists by attempting to open it in read mode; if it doesn't exist, jump to full_scan
    BTreeIndex indexFile;
    if (indexFile.open(table + ".idx", 'r') != 0){
        goto full_scan;
    }
    
    
    // check attr value, 2 and 3 require more that what is stored in the B+ tree
    if (attr == 2 || attr == 3) {
        onlyKeys = false;
    }
    
    for (size_t j = 0; j < cond.size(); j++) {
        // process each condition in the vector
        if (cond[j].attr == 1){
            switch (cond[j].comp) {
              case SelCond::EQ:
                // if no equals key has been set, set the flag and store the equals key
                if (useEqualsKey == false){
                    useEqualsKey = true;
                    equalsKey = atoi(cond[j].value);
                    
                    // If the equals key is outside a previously specified range, skip to cleanup
                    if (equalsKey < minKey || (equalsKey == minKey && includeMin == false)){
                        goto index_cleanup;
                    }
                    if (maxKey < equalsKey || (equalsKey == maxKey && includeMax == false)){
                        goto index_cleanup;
                    }
                }
                else {
                    // if two different EQ values are specified, skip to cleanup
                    if(atoi(cond[j].value) != equalsKey){
                        goto index_cleanup;
                    }
                }
                break;
              case SelCond::NE:
                // add to nonRangeConds
                nonRangeConds.push_back(j);
                break;
              case SelCond::GT:
                // if greater than current min, or equal to it, update min
                if (atoi(cond[j].value) >= minKey){
                    minKey = atoi(cond[j].value);
                    includeMin = false;
                    
                    // If the equals key is outside the newly specified range, skip to cleanup
                    if (equalsKey <= minKey && useEqualsKey == true){
                        goto index_cleanup;
                    }
                }
                break;
              case SelCond::LT:
                // if less than current max, or equal to it, update max
                if (atoi(cond[j].value) <= maxKey){
                    maxKey = atoi(cond[j].value);
                    includeMax = false;
                    
                    // If the equals key is outside the newly specified range, skip to cleanup
                    if (equalsKey >= maxKey && useEqualsKey == true){
                        goto index_cleanup;
                    }
                }
                break;
              case SelCond::GE:
                // if greater than current min, update min
                if (atoi(cond[j].value) > minKey){
                    minKey = atoi(cond[j].value);
                    includeMin = true;
                    
                    // If the equals key is outside the newly specified range, skip to cleanup
                    if (equalsKey < minKey && useEqualsKey == true){
                        goto index_cleanup;
                    }
                }
                break;
              case SelCond::LE:
                // if less than current max, update max
                if (atoi(cond[j].value) < maxKey){
                    maxKey = atoi(cond[j].value);
                    includeMax = true;
                    
                    // If the equals key is outside the newly specified range, skip to cleanup
                    if (equalsKey > maxKey && useEqualsKey == true){
                        goto index_cleanup;
                    }
                }
                break;
            }
        }
        else {
            // indicate that actual records must be accessed and add to nonRangeConds
            onlyKeys = false;
            nonRangeConds.push_back(j);
        }
    }
    
    // if no range has been specified, and conditions require the actual records, then close index and jump to full_scan here
    if (minKey == INT_MIN && maxKey == INT_MAX && useEqualsKey == false && onlyKeys == false) {
        if ((rc = indexFile.close()) != 0) {
            cerr << "Error closing indexfile." << endl;
        }
        goto full_scan;
    }

    // pick proper starting point, based on equalKey and minKey
    IndexCursor cursor;
    if (useEqualsKey == true){
        indexFile.locate(equalsKey, cursor);
    }
    else if (minKey != INT_MIN) {
        indexFile.locate(minKey, cursor);
    }
    else {
        indexFile.getLeftmost(cursor);
    }
    
    // loop while there are keys to be read
    while ((rc = indexFile.readForward(cursor, key, rid)) == 0) {
        // range checks here, end the loop early if equals / max limits are violated, skip to the next entry if min limit is violated
        if (useEqualsKey == true){
            if (key != equalsKey) {
                break;
            }
        }
        else {
            if (includeMin == true) {
                if (key < minKey) {
                    goto next_entry;
                }
            }
            else {
                if (key <= minKey) {
                    goto next_entry;
                }
            }
            
            if (includeMax == true) {
                if (key > maxKey) {
                    break;
                }
            }
            else {
                if (key >= maxKey) {
                    break;
                }
            }
        }
       
        // if necessary, read the tuple
        if (onlyKeys == false) {
            if ((rc = rf.read(rid, key, value)) < 0) {
                fprintf(stderr, "Error: while reading a tuple from table %s\n", table.c_str());
                goto exit_select;
            }
        }

        // check the conditions on the tuple
        for (unsigned k = 0; k < nonRangeConds.size(); k++) {
        // compute the difference between the tuple value and the condition value
            switch (cond[nonRangeConds[k]].attr) {
              case 1:
                diff = key - atoi(cond[nonRangeConds[k]].value);
                break;
              case 2:
                diff = strcmp(value.c_str(), cond[nonRangeConds[k]].value);
                break;
            }

            // skip the tuple if any condition is not met
            switch (cond[nonRangeConds[k]].comp) {
              case SelCond::EQ:
                if (diff != 0) goto next_entry;
                break;
              case SelCond::NE:
                if (diff == 0) goto next_entry;
                break;
              case SelCond::GT:
                if (diff <= 0) goto next_entry;
                break;
              case SelCond::LT:
                if (diff >= 0) goto next_entry;
                break;
              case SelCond::GE:
                if (diff < 0) goto next_entry;
                break;
              case SelCond::LE:
                if (diff > 0) goto next_entry;
                break;
            }
        }

        // the condition is met for the tuple. 
        // increase matching tuple counter
        count++;

        // print the tuple 
        switch (attr) {
          case 1:  // SELECT key
            fprintf(stdout, "%d\n", key);
            break;
          case 2:  // SELECT value
            fprintf(stdout, "%s\n", value.c_str());
            break;
          case 3:  // SELECT *
            fprintf(stdout, "%d '%s'\n", key, value.c_str());
            break;
        }

        // move to the next tuple
        next_entry:
        /*continue*/;
    }
    
    // if the loop ended for reasons other than going out of the range for the query or reaching the end of the tree, print error message
    if (rc != 0 && rc != RC_END_OF_TREE){
        cerr << "Error reading from indexfile." << endl;
    }
    
    // index cleanup here, make sure to close index
    index_cleanup:
    if ((rc = indexFile.close()) != 0) {
        cerr << "Error closing indexfile." << endl;
    }
    
    // jump to cleanup_step here
    goto cleanup_step;
    
    // scan the table file from the beginning
    full_scan:
    rid.pid = rid.sid = 0;
    while (rid < rf.endRid()) {
        // read the tuple
        if ((rc = rf.read(rid, key, value)) < 0) {
            fprintf(stderr, "Error: while reading a tuple from table %s\n", table.c_str());
            goto exit_select;
        }

        // check the conditions on the tuple
        for (unsigned i = 0; i < cond.size(); i++) {
        // compute the difference between the tuple value and the condition value
            switch (cond[i].attr) {
              case 1:
                diff = key - atoi(cond[i].value);
                break;
              case 2:
                diff = strcmp(value.c_str(), cond[i].value);
                break;
            }

            // skip the tuple if any condition is not met
            switch (cond[i].comp) {
              case SelCond::EQ:
                if (diff != 0) goto next_tuple;
                break;
              case SelCond::NE:
                if (diff == 0) goto next_tuple;
                break;
              case SelCond::GT:
                if (diff <= 0) goto next_tuple;
                break;
              case SelCond::LT:
                if (diff >= 0) goto next_tuple;
                break;
              case SelCond::GE:
                if (diff < 0) goto next_tuple;
                break;
              case SelCond::LE:
                if (diff > 0) goto next_tuple;
                break;
            }
        }

        // the condition is met for the tuple. 
        // increase matching tuple counter
        count++;

        // print the tuple 
        switch (attr) {
          case 1:  // SELECT key
            fprintf(stdout, "%d\n", key);
            break;
          case 2:  // SELECT value
            fprintf(stdout, "%s\n", value.c_str());
            break;
          case 3:  // SELECT *
            fprintf(stdout, "%d '%s'\n", key, value.c_str());
            break;
        }

        // move to the next tuple
        next_tuple:
        ++rid;
    }

    // cleanup operations both approaches require
    cleanup_step:
    // print matching tuple count if "select count(*)"
    if (attr == 4) {
        fprintf(stdout, "%d\n", count);
    }
    rc = 0;

    // close the table file and return
    exit_select:
    rf.close();
    return rc;
}

RC SqlEngine::load(const string& table, const string& loadfile, bool index)
{
    // Open loadfile
    ifstream load;
    load.open(loadfile.c_str());
    if(!load.is_open()) {
        cerr << "Unable to open loadfile." <<endl;
        return RC_FILE_OPEN_FAILED;
    }

    // Generate table name, open tablefile
    const string table_filename = table + ".tbl";
    RecordFile rf;
    RC rc = rf.open(table_filename, 'w');
    if (rc != 0) {
        cerr << "Unable to open tablefile." <<endl;
        return rc;
    }
    
    // Generate index name, open indexfile if requested
    const string index_filename = table + ".idx";
    BTreeIndex indexFile;
    if (index){
        rc = indexFile.open(index_filename, 'w');
        if (rc != 0) {
            cerr << "Unable to open indexfile." <<endl;
            return rc;
        }
    }

    // Set up helper variables to for loading
    string line;
    unsigned long long linenum = 0;
    int key;
    string value;
    RecordId rid;
    
    // Load lines, parse, and store them
    for (;;) {
        // Extract a line
        load.clear();
        getline(load, line);
        linenum++;
        
        // If nothing was extracted, then stop looping
        if(load.eof() && line.empty()){
            break;
        }
        // If an unknown error occurred, report it and stop extraction
        if (load.bad()) {
            cerr << "Error reading file, line num: " << linenum << endl;
            break;
        }
    
        // Parse line, and load into file
        if (parseLoadLine(line, key, value) != RC_INVALID_FILE_FORMAT) {
            if(rf.append(key, value, rid) != 0){
                cerr << "Error adding record, line: " << linenum << endl;
            }
            else {
                // If no errors, and an index is requested, add to index
                if (index) {
                    indexFile.insert(key, rid);
                }
            }
        }
        else {
            cerr << "Invalid format, line: " << linenum << endl;
        }
    }

    
    // Close tablefile first in case of erro, since I don't think it will get cleaned up automatically like loadfile's ifstream
    rc = rf.close();
    if (rc != 0) {
        cerr << "Unable to close tablefile." <<endl;
        return rc;
    }
    
    // If opened, close the index file -- note that both the index file and the loadfile get closed automatically once their objects are deleted
    if (index){
        rc = indexFile.close();
        if (rc != 0) {
            cerr << "Unable to close indexfile." << endl;
            return rc;
        }
    }
    
    // Close loadfile
    load.clear();
    load.close();
    if(load.fail()) {
        cerr << "Unable to close tablefile." << endl;
        return RC_FILE_CLOSE_FAILED;
    }
    
    return 0;
}

RC SqlEngine::parseLoadLine(const string& line, int& key, string& value)
{
    const char *s;
    char        c;
    string::size_type loc;
    
    // ignore beginning white spaces
    c = *(s = line.c_str());
    while (c == ' ' || c == '\t') { c = *++s; }

    // get the integer key value
    key = atoi(s);

    // look for comma
    s = strchr(s, ',');
    if (s == NULL) { return RC_INVALID_FILE_FORMAT; }

    // ignore white spaces
    do { c = *++s; } while (c == ' ' || c == '\t');
    
    // if there is nothing left, set the value to empty string
    if (c == 0) { 
        value.erase();
        return 0;
    }

    // is the value field delimited by ' or "?
    if (c == '\'' || c == '"') {
        s++;
    } else {
        c = '\n';
    }

    // get the value string
    value.assign(s);
    loc = value.find(c, 0);
    if (loc != string::npos) { value.erase(loc); }

    return 0;
}
